package com.dbflow5.livedata;

import com.dbflow5.config.DBFlowDatabase;
import com.dbflow5.config.FlowManager;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.observing.OnTableChangedObserver;
import com.dbflow5.observing.TableObserver;
import com.dbflow5.query.ModelQueriable;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

public class QueryLiveData<T, R> extends LiveData<R>{
    private final ModelQueriable<T> modelQueriable;
    private final BiFunction<ModelQueriable<T>, DatabaseWrapper, R> evalFn;

    private Set<Class<?>> associatedTables;

    private final OnTableChangedObserver onTableChangedObserver;

    public QueryLiveData(ModelQueriable<T> modelQueriable, BiFunction<ModelQueriable<T>, DatabaseWrapper, R> evalFn) {
        this.modelQueriable = modelQueriable;
        this.evalFn =evalFn;

        if(modelQueriable.extractFrom() != null) {
            Set<Class<?>> setClasses = modelQueriable.extractFrom().associatedTables();
            if(setClasses == null){
                setClasses = Collections.singleton(modelQueriable.table());
            }
            associatedTables = setClasses;
        }

        onTableChangedObserver = new OnTableChangedObserver(new ArrayList<>(associatedTables)) {
            @Override
            protected void onChanged(Set<Class<?>> tables) {
                if (!tables.isEmpty()) {
                    evaluateEmission(tables.stream().findFirst().get());
                }
            }
        };
    }

    private void evaluateEmission(Class<?> table) {
        if(table == null) {
            table =  modelQueriable.table();
        }
        FlowManager.databaseForTable(table, db -> null)
                .beginTransactionAsync((Function<DatabaseWrapper, R>) databaseWrapper -> evalFn.apply(modelQueriable, databaseWrapper)).execute(null, null, null, (rTransaction, r) -> {
                    setValue(r);
                    return null;
                });
    }

    @Override
    public void onActive() {
        super.onActive();

        DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables.stream().findFirst().get());
        // force initialize the db
        db.getWritableDatabase();

        TableObserver observer = db.tableObserver();
        observer.addOnTableChangedObserver(onTableChangedObserver);

        // trigger initial emission on active.
        evaluateEmission(null);
    }

    @Override
    public void onInactive() {
        super.onInactive();
        DBFlowDatabase db = FlowManager.getDatabaseForTable(associatedTables.stream().findFirst().get());
        TableObserver observer = db.tableObserver();
        observer.removeOnTableChangedObserver(onTableChangedObserver);
    }

    /**
     * Return a new [LiveData] instance. Specify using the [evalFn] what query to run.
     *
     * @param q ModelQueriable
     * @param evalFn BiFunction
     * @return [LiveData] instance
     */
    public static <T, Q extends ModelQueriable<T>, R> LiveData<R> toLiveData(Q q, BiFunction<ModelQueriable<T>, DatabaseWrapper, R> evalFn) {
        return new QueryLiveData<>(q, evalFn);
    }
}
